﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FileMerge.Enums;
using FileMerge.Models;

namespace FileMerge.Helpers
{
    /// <summary>
    /// 文件合并帮助
    /// </summary>
    public static class MergeHelper
    {
        /// <summary>
        /// 合并
        /// </summary>
        /// <param name="config">配置</param>
        /// <returns></returns>
        public static MergeResult Merge(Configuration config)
        {
            var result = new MergeResult { Succeed = true };

            if (File.Exists(config.Output))
            {
                if (!config.Overwrite)
                {
                    result.Succeed = false;
                    result.Message = "Error: Output file already existed.";

                    return result;
                }
                else
                {
                    try { File.Delete(config.Output); }

                    catch (IOException e)
                    {
                        result.Succeed = false;
                        result.Message = $"Error: Failed to delete existed output file. {e.Message}\r\n";

                        return result;
                    }
                }
            }

            List<string> files;

            try { files = Directory.GetFiles(config.BaseDirectory, config.SearchPattern, config.SearchOption).ToList(); }

            catch (IOException e)
            {
                result.Succeed = false;
                result.Message = $"Error: Failed to get files. {e.Message}\r\n";
                return result;
            }

            if (files != null && config.OnlyManually)
            {
                files = files.Where(f => config.ManuallyOrder.Contains(Path.GetFileName(f))).ToList();
            }

            if (files == null || files.Count == 0)
            {
                result.Message = "Warning: No matched files.";
                return result;
            }

            foreach (var pattern in config.Ignored)
            {
                List<string> toIgnore = null;

                try { toIgnore = Directory.GetFiles(config.BaseDirectory, pattern, config.SearchOption).ToList(); }

                catch (IOException e) { result.Message = $"Warning: Failed to get ignored files. {e.Message}\r\n"; }

                if (toIgnore == null || toIgnore.Count == 0)
                    continue;

                foreach (var item in toIgnore)
                {
                    if (!files.Contains(item))
                        continue;

                    files.Remove(item);
                    result.Ignored.Add(item);
                }
            }

            var info = files.Select(f => new FileInfo(f)).ToList();

            info = SetOrder(info, config.Orders, config.ManuallyOrder);

            result.Merged.AddRange(info.Select(i => i.FullName));

            var outputDir = Path.GetDirectoryName(config.Output);

            if (!Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); }

            try
            {
                using (var output = File.OpenWrite(config.Output))
                using (var sw = new StreamWriter(output, config.GetEncoding()))
                {
                    sw.WriteLine(TemplateHelper.Parse(config.Header));

                    for (int i = 0; i < info.Count; i++)
                    {
                        sw.WriteLine(TemplateHelper.Parse(config.Summary, info[i], i + 1));
                    }

                    for (int i = 0; i < info.Count; i++)
                    {
                        using (var input = info[i].OpenRead())
                        using (var sr = new StreamReader(input, config.GetEncoding()))
                        {
                            var content = sr.ReadToEnd();

                            sw.WriteLine(TemplateHelper.Parse(config.FileHeader, info[i], i + 1));
                            sw.WriteLine(content);
                            sw.WriteLine(TemplateHelper.Parse(config.FileFooter, info[i], i + 1));
                        }
                    }

                    sw.WriteLine(TemplateHelper.Parse(config.Footer));
                }
            }
            catch (IOException e)
            {
                result.Succeed = false;
                result.Message += $"Error: Failed to merge files. {e.Message}\r\n";
            }

            return result;
        }

        /// <summary>
        /// 设置文件排序
        /// </summary>
        /// <param name="info">文件信息</param>
        /// <param name="orders">排序条件</param>
        /// <param name="manuallyOrder">手工排序</param>
        /// <returns></returns>
        private static List<FileInfo> SetOrder(List<FileInfo> info, List<Order> orders, List<string> manuallyOrder)
        {
            if (info == null || info.Count() == 0 || orders == null || orders.Count == 0)
                return info;

            IOrderedEnumerable<FileInfo> toOrder = null;

            if (manuallyOrder != null && manuallyOrder.Count != 0)
            {
                toOrder = info.OrderBy(i =>
                {
                    var index = manuallyOrder.IndexOf(i.Name);

                    return index == -1 ? int.MaxValue : index;
                });
            }

            foreach (var order in orders)
            {
                if (order.OrderType == OrderType.Ascending)
                {
                    switch (order.FileOrder)
                    {
                        case FileOrder.Name:
                            toOrder = toOrder == null ?
                                info.OrderBy(f => f.FullName) :
                                toOrder.ThenBy(f => f.FullName);
                            break;

                        case FileOrder.CreationTime:
                            toOrder = toOrder == null ?
                                info.OrderBy(f => f.CreationTime) :
                                toOrder.ThenBy(f => f.CreationTime);
                            break;

                        case FileOrder.LastWriteTime:
                            toOrder = toOrder == null ?
                                info.OrderBy(f => f.LastWriteTime) :
                                toOrder.ThenBy(f => f.LastWriteTime);
                            break;

                        default:
                            break;
                    }
                }
                else
                {
                    switch (order.FileOrder)
                    {
                        case FileOrder.Name:
                            toOrder = toOrder == null ?
                                info.OrderByDescending(f => f.FullName) :
                                toOrder.ThenByDescending(f => f.FullName);
                            break;

                        case FileOrder.CreationTime:
                            toOrder = toOrder == null ?
                                info.OrderByDescending(f => f.CreationTime) :
                                toOrder.ThenByDescending(f => f.CreationTime);
                            break;

                        case FileOrder.LastWriteTime:
                            toOrder = toOrder == null ?
                                info.OrderByDescending(f => f.LastWriteTime) :
                                toOrder.ThenByDescending(f => f.LastWriteTime);
                            break;

                        default:
                            break;
                    }
                }
            }

            return toOrder.ToList() ?? info;
        }
    }
}
